package com.future.service.Impl;

import cn.hutool.core.lang.TypeReference;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.future.allUtils.utils.CacheClient;
import com.future.allUtils.utils.SecurityUtils;
import com.future.domain.DTO.MediaResourceDTO;
import com.future.domain.MediaResource;
import com.future.domain.Result;
import com.future.domain.vo.MediaResourceVO;
import com.future.exception.ServiceException;
import com.future.mapper.MediaResourceMapper;
import com.future.service.MediaResourceService;
import io.minio.*;
import io.minio.errors.ErrorResponseException;
import io.minio.errors.InsufficientDataException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import static com.future.allUtils.constants.RedisCacheConstants.CACHE_NORMAL_TTL;

@Slf4j
@Service
public class MediaResourceServiceImpl extends ServiceImpl<MediaResourceMapper, MediaResource> implements MediaResourceService {
    // 缓存键前缀常量
    static final String CACHE_MEDIA_INFO_KEY = "media:info:";       // 单个媒体信息缓存键前缀
    private static final String CACHE_MEDIA_TYPE_KEY = "media:type:";     // 按类型分页缓存键前缀
    private static final String CACHE_MEDIA_USER_KEY = "media:user:";     // 按用户分页缓存键前缀
    private static final String CACHE_MEDIA_USER_TYPE_KEY = "media:user:type:"; // 按用户+类型分页缓存键前缀
    private static final String CACHE_MEDIA_PENDING_KEY = "media:pending:";


    @Autowired
    private MinioClient minioClient;
    @Autowired
    private MediaResourceMapper mediaResourceMapper;
    @Autowired // 注入缓存工具类
    private CacheClient cacheClient;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    // ---------------------- 通用转换方法 ----------------------
    private MediaResourceVO convertToVO(MediaResource media) {
        MediaResourceVO vo = new MediaResourceVO();
        BeanUtils.copyProperties(media, vo);
        return vo;
    }

    /**
     * 审核媒体资源
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result approveMedia(Long id, Integer approved, String reason) {
        // 参数校验
        if (id == null || approved == null || (approved != 0 && approved != 1 && approved != 2)) {
            return Result.fail("参数无效，请检查后重试");
        }

        // 查询媒体资源
        MediaResource media = this.getById(id);
        if (media == null) {
            return Result.fail("媒体资源不存在");
        }

        // 已经审核过的资源不再处理
        if (media.getIsApproved() != 0) {
            return Result.fail("该资源已审核，无需重复操作");
        }

        // 更新审核状态
        media.setIsApproved(approved);
        media.setReason(reason);
        boolean updateSuccess = this.updateById(media);
        if (!updateSuccess) {
            return Result.fail("审核操作失败");
        }

        // 记录审核日志
        if (approved == 1) {
            log.info("媒体资源 [{}] 审核通过", id);
        } else {
            log.info("媒体资源 [{}] 审核拒绝，原因：{}", id, reason);
        }

        // 缓存更新策略：删除相关缓存
        cacheClient.deleteByPattern(CACHE_MEDIA_PENDING_KEY + "*");
        cacheClient.deleteByPattern(CACHE_MEDIA_INFO_KEY + id); // 单个资源缓存
        cacheClient.deleteByPattern(CACHE_MEDIA_TYPE_KEY + media.getFileType() + ":*"); // 类型分页缓存
        cacheClient.deleteByPattern(CACHE_MEDIA_USER_KEY + media.getUploaderId() + ":*"); // 用户分页缓存
        cacheClient.deleteByPattern(CACHE_MEDIA_USER_TYPE_KEY + media.getUploaderId() + ":" + media.getFileType() + ":*"); // 用户+类型缓存

        return Result.ok(approved == 1 ? "审核通过" : "审核拒绝");
    }

    /**
     * 获取待审核的媒体资源列表
     */
    @Override
    public Result getPendingMedia(Integer pageNum, Integer pageSize) {
        // 处理分页参数
        pageNum = (pageNum == null || pageNum < 1) ? 1 : pageNum;
        pageSize = (pageSize == null || pageSize < 1) ? 10 : pageSize;

        // 缓存键：media:pending:1:10（页码+页大小）
        String cacheKey = "media:pending:" + pageNum + ":" + pageSize;

        // 1. 先查缓存
        String json = stringRedisTemplate.opsForValue().get(cacheKey);
        if (StringUtils.isNotBlank(json)) {
            try {
                // 使用具体的Page类而非IPage接口进行反序列化
                Page<MediaResourceVO> voPage = JSONUtil.toBean(json, new TypeReference<Page<MediaResourceVO>>() {
                        },
                        true);
                if (voPage != null && voPage.getRecords() != null) {
                    return Result.ok(voPage);
                } else {
                    log.warn("缓存数据格式异常，键：{}", cacheKey);
                }
            } catch (Exception e) {
                log.error("缓存反序列化失败，键：{}", cacheKey, e);
            }
        }

        // 2. 缓存未命中，查数据库
        Page<MediaResourceVO> page = new Page<>(pageNum, pageSize);
        Page<MediaResourceVO> voPage
                = mediaResourceMapper.selectPendingMediaResources(page);

        // 3. 写入缓存（缓存时间10分钟，随机偏移）
        if (voPage != null && voPage.getRecords() != null) {
            log.info("缓存写入成功，键：{}", cacheKey);
            long randomTime = cacheClient.randomObjectTimes(600L); // 10分钟+随机偏移
            cacheClient.set(cacheKey, voPage, randomTime, TimeUnit.SECONDS);
        } else {
            log.warn("查询结果为空，不写入缓存，键：{}", cacheKey);
        }

        return Result.ok(voPage);
    }

    private List<MediaResourceVO> convertToVOList(List<MediaResource> mediaList) {
        List<MediaResourceVO> voList = new ArrayList<>();
        for (MediaResource media : mediaList) {
            voList.add(convertToVO(media));
        }
        return voList;
    }

    // ---------------------- 异常处理/工具方法 ----------------------
    private void handleUploadException(Exception e, String bucketName, String objectName) {
        log.error("文件上传失败: {}", e.getMessage());
        if (objectName != null) {
            try {
                minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
                log.info("已清理未完成的上传: {}", objectName);
            } catch (Exception cleanupEx) {
                log.error("清理上传文件失败: {}", cleanupEx.getMessage());
            }
        }
        if (e instanceof InsufficientDataException) {
            throw new ServiceException("上传数据不完整，请重试");
        } else if (e instanceof ErrorResponseException) {
            ErrorResponseException ere = (ErrorResponseException) e;
            switch (ere.errorResponse().code()) {
                case "EntityTooLarge":
                    throw new ServiceException("文件大小超过限制");
                case "AccessDenied":
                    throw new ServiceException("存储桶访问权限不足");
                default:
                    throw new ServiceException("存储服务错误: " + ere.getMessage());
            }
        } else if (e instanceof IOException) {
            throw new ServiceException("文件读取失败: " + e.getMessage());
        }
    }

    private String getFileExtension(String filename) {
        if (filename == null || filename.lastIndexOf('.') == -1) {
            return "";
        }
        return filename.substring(filename.lastIndexOf('.') + 1);
    }

    private String generateObjectName(String extension) {
        String uuid = UUID.randomUUID().toString().replace("-", "");
        return StringUtils.isNotBlank(extension) ? uuid + "." + extension.toLowerCase() : uuid;
    }

    // ---------------------- 业务方法 ----------------------

    /**
     * 1. 上传媒体到MinIO
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result uploadMedia(MultipartFile file, int type) {
        if (file.isEmpty()) {
            return Result.fail("请选择文件");
        }
        if (type != 1 && type != 2) {
            return Result.fail("请选择正确的文件类型");
        }

        Long uploaderId = SecurityUtils.getCurrentUserId();
        String bucketName = type == 1 ? "picture" : "video";
        String originalFilename = file.getOriginalFilename();
        String filePath = null;
        String objectName = null;

        try {
            String fileExtension = getFileExtension(originalFilename);
            objectName = generateObjectName(fileExtension);

            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .stream(file.getInputStream(), file.getSize(), -1)
                            .contentType(file.getContentType())
                            .build()
            );

            filePath = "http://47.110.147.12:19000/" + bucketName + "/" + objectName;
            MediaResource media = new MediaResource();
            media.setFileName(originalFilename);
            media.setFilePath(filePath);
            media.setFileType(type == 1 ? "图片" : "视频");
            media.setFileSize(file.getSize());
            media.setUploaderId(uploaderId);
            media.setIsApproved(0);
            media.setDownloadCount(0);
            media.setCreatedAt(new Date());

            return Result.ok(convertToVO(media));
        } catch (Exception e) {
            handleUploadException(e, bucketName, objectName);
            throw new ServiceException("文件上传失败: " + e.getMessage());
        }
    }

    /**
     * 2. 仅删除MinIO文件
     */
    @Override
    public Result deleteMinioMedia(String fileType, String filePath) {
        String bucketName = fileType.equals("图片") ? "picture" : "video";
        String objectName = filePath.substring(filePath.lastIndexOf("/") + 1);

        try {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());

        } catch (Exception e) {
            throw new ServiceException("MinIO删除失败: " + e.getMessage(), e);
        }
        return Result.ok();
    }

    /**
     * 3. 上传媒体到数据库（新增操作，删除用户相关缓存）
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result uploadTODB(MediaResourceDTO mediaResourceDTO) {
        // 参数校验
        if (mediaResourceDTO == null || StringUtils.isBlank(mediaResourceDTO.getTitle())
                || StringUtils.isBlank(mediaResourceDTO.getFilePath()) || mediaResourceDTO.getFileType() == null) {
            throw new ServiceException("参数不完整，请检查后重试");
        }

        Long uploaderId = SecurityUtils.getCurrentUserId();
        if (uploaderId == null) {
            throw new ServiceException("未获取到当前用户信息，请登录");
        }

        // 验证MinIO文件存在
        String bucketName = mediaResourceDTO.getFileType().equals("图片") ? "picture" : "video";
        String objectName = mediaResourceDTO.getFilePath().substring(mediaResourceDTO.getFilePath().lastIndexOf("/") + 1);
        try {
            minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
        } catch (Exception e) {
            log.error("文件不存在或验证失败: {}", e.getMessage());
            throw new ServiceException("文件不存在，请重新上传");
        }

        // 构建实体并保存
        MediaResource mediaResource = new MediaResource();
        BeanUtils.copyProperties(mediaResourceDTO, mediaResource);
        mediaResource.setUploaderId(uploaderId);
        mediaResource.setIsApproved(0);
        mediaResource.setDownloadCount(0);
        mediaResource.setViews(0L);
        mediaResource.setLikeCount(0L);
        mediaResource.setCollectionCount(0L);
        mediaResource.setCreatedAt(new Date());

        boolean saveSuccess = this.save(mediaResource);
        if (!saveSuccess) {
            throw new ServiceException("数据库存储失败");
        }

        // 缓存更新策略：删除用户相关的分页缓存（避免新增数据不显示）
        String userCachePattern = CACHE_MEDIA_USER_KEY + uploaderId + ":*"; // 匹配用户所有分页缓存
        String userTypeCachePattern = CACHE_MEDIA_USER_TYPE_KEY + uploaderId + ":" + mediaResourceDTO.getFileType() + ":*"; // 用户+类型分页缓存
        cacheClient.deleteByPattern(userCachePattern);
        cacheClient.deleteByPattern(userTypeCachePattern);

        // 删除类型分页缓存（避免新增数据不显示）
        String typeCachePattern = CACHE_MEDIA_TYPE_KEY + mediaResourceDTO.getFileType() + ":*";
        cacheClient.deleteByPattern(typeCachePattern);

        return Result.ok(convertToVO(mediaResource));
    }

    /**
     * 4. 完整删除（MinIO+数据库，删除所有相关缓存）
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result deleteMedia(Long id) {
        // 1. 查询媒体信息
        MediaResource media = this.getById(id);
        if (media == null) {
            return Result.fail("数据库未获取到媒体信息");
        }
        Long uploaderId = media.getUploaderId();
        String fileType = media.getFileType();

        // 2. 删除MinIO文件
        String bucketName = fileType.equals("图片") ? "picture" : "video";
        String objectName = media.getFilePath().substring(media.getFilePath().lastIndexOf("/") + 1);
        try {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
        } catch (Exception e) {
            throw new ServiceException("MinIO删除失败: " + e.getMessage(), e);
        }

        // 3. 删除数据库记录
        boolean deleted = this.removeById(id);
        if (!deleted) {
            return Result.fail("数据库删除失败");
        }

        // 4. 缓存更新策略：删除所有相关缓存
        cacheClient.deleteByPattern(CACHE_MEDIA_INFO_KEY + id); // 单个媒体信息缓存
        cacheClient.deleteByPattern(CACHE_MEDIA_USER_KEY + uploaderId + ":*"); // 用户分页缓存
        cacheClient.deleteByPattern(CACHE_MEDIA_USER_TYPE_KEY + uploaderId + ":" + fileType + ":*"); // 用户+类型分页缓存
        cacheClient.deleteByPattern(CACHE_MEDIA_TYPE_KEY + fileType + ":*"); // 类型分页缓存

        return Result.ok();
    }

    /**
     * 5. 获取媒体详情（带缓存，解决穿透+击穿）
     */
    @Override
    public Result getMediaInfo(Long id) {
        // 1. 使用缓存工具类查询（解决穿透+击穿，缓存时间1小时）
        MediaResource media = cacheClient.queryWithPassThrough(
                CACHE_MEDIA_INFO_KEY,  // 缓存键前缀
                id,                    // 业务ID
                MediaResource.class,   // 返回类型
                this::getById,         // 数据库查询回调
                CACHE_NORMAL_TTL,      // 基础过期时间（1小时）
                TimeUnit.SECONDS       // 时间单位
        );

        if (media == null) {
            return Result.fail("媒体资源不存在");
        }

        // 2. 浏览量+1（更新操作，先更新数据库再删除缓存）
        media.setViews(media.getViews() + 1);
        this.updateById(media);
        // 缓存更新策略：删除该媒体的详情缓存（下次查询重新加载最新数据）
        cacheClient.deleteByPattern(CACHE_MEDIA_INFO_KEY + id);

        // 3. 返回VO
        MediaResourceVO vo = convertToVO(media);
        vo.setUploaderIdStr(String.valueOf(media.getUploaderId()));
        return Result.ok(vo);
    }

    /**
     * 6. 按类型分页查询（带缓存，解决穿透）
     */
    @Override
    public Result listByType(String fileType, Integer pageNum, Integer pageSize) {
        // 处理分页参数
        pageNum = (pageNum == null || pageNum < 1) ? 1 : pageNum;
        pageSize = (pageSize == null || pageSize < 1) ? 10 : pageSize;

        // 缓存键：media:type:图片:1:10（类型+页码+页大小）
        String cacheKey = CACHE_MEDIA_TYPE_KEY + fileType + ":" + pageNum + ":" + pageSize;

        // 1. 先查缓存
        String json = stringRedisTemplate.opsForValue().get(cacheKey);
        if (StringUtils.isNotBlank(json)) {
            try {
                // 使用具体的Page类而非IPage接口进行反序列化
                Page<MediaResourceVO> voPage = JSONUtil.toBean(json, new TypeReference<Page<MediaResourceVO>>() {
                }, true);
                if (voPage != null && voPage.getRecords() != null) {
                    return Result.ok(voPage);
                } else {
                    log.warn("缓存数据格式异常，键：{}", cacheKey);
                }
            } catch (Exception e) {
                log.error("缓存反序列化失败，键：{}", cacheKey, e);
            }
        }

        // 2. 缓存未命中，查数据库
        Page<MediaResource> page = new Page<>(pageNum, pageSize);
        LambdaQueryWrapper<MediaResource> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(MediaResource::getFileType, fileType)
                .eq(MediaResource::getIsApproved, 1)
                .orderByDesc(MediaResource::getViews, MediaResource::getLikeCount, MediaResource::getCollectionCount, MediaResource::getCreatedAt);
        Page<MediaResource> resultPage = this.page(page, wrapper);

        // 3. 转换为VO并写入缓存（缓存时间30分钟，随机偏移避免雪崩）
        IPage<MediaResourceVO> voPage = resultPage.convert(this::convertToVO);
        for (MediaResourceVO vo : voPage.getRecords()){
            vo.setUploaderIdStr(String.valueOf(vo.getUploaderId()));
        }
        if (voPage != null && voPage.getRecords() != null) {
            log.info("缓存写入成功，键：{}", cacheKey);
            long randomTime = cacheClient.randomObjectTimes(1800L); // 30分钟+1-10秒随机
            cacheClient.set(cacheKey, voPage, randomTime, TimeUnit.SECONDS);
        } else {
            log.warn("查询结果为空，不写入缓存，键：{}", cacheKey);
        }

        return Result.ok(voPage);
    }

    /**
     * 7. 按用户分页查询（带缓存，解决穿透）
     */
    @Override
    public Result listByUser(Integer pageNum, Integer pageSize) {
        Long userId = SecurityUtils.getCurrentUserId();
        if (userId == null) {
            return Result.fail("未获取到当前用户信息，请登录");
        }

        // 处理分页参数
        pageNum = (pageNum == null || pageNum < 1) ? 1 : pageNum;
        pageSize = (pageSize == null || pageSize < 1) ? 10 : pageSize;

        // 缓存键：media:user:123:1:10（用户ID+页码+页大小）
        String cacheKey = CACHE_MEDIA_USER_KEY + userId + ":" + pageNum + ":" + pageSize;

        // 1. 先查缓存
        String json = stringRedisTemplate.opsForValue().get(cacheKey);
        if (StringUtils.isNotBlank(json)) {
            try {
                // 使用具体的Page类而非IPage接口进行反序列化
                Page<MediaResourceVO> voPage = JSONUtil.toBean(json, new TypeReference<Page<MediaResourceVO>>() {
                }, true);
                if (voPage != null && voPage.getRecords() != null) {
                    return Result.ok(voPage);
                } else {
                    log.warn("缓存数据格式异常，键：{}", cacheKey);
                }
            } catch (Exception e) {
                log.error("缓存反序列化失败，键：{}", cacheKey, e);
            }
        }

        // 2. 缓存未命中，查数据库
        Page<MediaResource> page = new Page<>(pageNum, pageSize);
        QueryWrapper<MediaResource> wrapper = new QueryWrapper<>();
        wrapper.eq("uploader_id", userId)
                .orderByDesc("created_at", "views", "like_count", "collection_count");
        Page<MediaResource> resultPage = this.page(page, wrapper);

        // 3. 转换为VO并写入缓存（缓存时间30分钟，随机偏移）
        IPage<MediaResourceVO> voPage = resultPage.convert(this::convertToVO);
        for (MediaResourceVO vo : voPage.getRecords()) {
           vo.setUploaderIdStr(String.valueOf(vo.getUploaderId()));
        }

        if (voPage != null && voPage.getRecords() != null) {
            log.info("缓存写入成功，键：{}", cacheKey);
            long randomTime = cacheClient.randomObjectTimes(1800L);
            cacheClient.set(cacheKey, voPage, randomTime, TimeUnit.SECONDS);
        } else {
            log.warn("查询结果为空，不写入缓存，键：{}", cacheKey);
        }

        return Result.ok(voPage);
    }

    /**
     * 8. 下载媒体资源（更新下载量，删除缓存）
     */
    @Override
    public Result downloadMedia(Long id) {
        // 1. 获取媒体信息
        MediaResource media = this.getById(id);
        if (media == null) {
            return Result.fail("数据库未获取到媒体信息");
        }

        // 2. 更新下载次数
        media.setDownloadCount(media.getDownloadCount() + 1);
        this.updateById(media);

        // 3. 缓存更新策略：删除相关缓存
        cacheClient.deleteByPattern(CACHE_MEDIA_INFO_KEY + id); // 单个资源缓存
        cacheClient.deleteByPattern(CACHE_MEDIA_TYPE_KEY + media.getFileType() + ":*"); // 类型分页缓存
        cacheClient.deleteByPattern(CACHE_MEDIA_USER_KEY + media.getUploaderId() + ":*"); // 用户分页缓存
        cacheClient.deleteByPattern(CACHE_MEDIA_USER_TYPE_KEY + media.getUploaderId() + ":" + media.getFileType() + ":*"); // 用户+类型缓存

        // 4. 从MinIO下载文件
        String bucketName = media.getFileType().equals("图片") ? "picture" : "video";
        String objectName = media.getFilePath().substring(media.getFilePath().lastIndexOf("/") + 1);
        //获取文件输入流
        try (InputStream stream = minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .build()
        )) {//将输入流转换为字节数组输出流
            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            int nRead;
            byte[] data = new byte[1024];
            while ((nRead = stream.read(data, 0, data.length)) != -1) {
                buffer.write(data, 0, nRead);
            }
            buffer.flush();
            //返回包含字节数据的成功结果
            return Result.ok(buffer.toByteArray());
        } catch (Exception e) {
            throw new ServiceException("下载失败: " + e.getMessage(), e);
        }
    }

    //下载大文件（流式传输）
    @Override
    public void downloadBigMedia(Long id, HttpServletResponse response) {
        // 1. 获取媒体信息
        MediaResource media = this.getById(id);
        if (media == null) {
            try {
                response.setContentType("application/json;charset=UTF-8");
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                response.getWriter().write("{\"code\":404,\"msg\":\"数据库未获取到媒体信息\"}");
                response.getWriter().flush();
            } catch (IOException e) {
                throw new ServiceException("响应失败: " + e.getMessage(), e);
            }
            return;
        }

        // 2. 更新下载次数
        media.setDownloadCount(media.getDownloadCount() + 1);
        this.updateById(media);

        // 3. 缓存更新策略：删除相关缓存
        cacheClient.deleteByPattern(CACHE_MEDIA_INFO_KEY + id); // 单个资源缓存
        cacheClient.deleteByPattern(CACHE_MEDIA_TYPE_KEY + media.getFileType() + ":*"); // 类型分页缓存
        cacheClient.deleteByPattern(CACHE_MEDIA_USER_KEY + media.getUploaderId() + ":*"); // 用户分页缓存
        cacheClient.deleteByPattern(CACHE_MEDIA_USER_TYPE_KEY + media.getUploaderId() + ":" + media.getFileType() + ":*"); // 用户+类型缓存

        // 4. 从MinIO下载文件并流式传输
        String bucketName = media.getFileType().equals("图片") ? "picture" : "video";
        String objectName = media.getFilePath().substring(media.getFilePath().lastIndexOf("/") + 1);

        // 设置响应头
        try {
            // 处理中文文件名乱码
            String encodedFileName = URLEncoder.encode(media.getFileName(), StandardCharsets.UTF_8.name());
            // 设置内容类型
            String contentType = media.getFileType().equals("图片") ? "image/*" : "video/*";

            response.setContentType(contentType);
            response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"");
            response.setHeader("Content-Length", String.valueOf(media.getFileSize()));// 告诉客户端文件总大小
            response.setHeader("Accept-Ranges", "bytes"); // 支持断点续传
            response.setStatus(HttpServletResponse.SC_OK);// 设置200成功状态码

            // 流式传输：直接对接MinIO输入流和响应输出流
            try (InputStream stream = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .build()
            );
                 OutputStream out = response.getOutputStream()) {

                byte[] buffer = new byte[8192]; // 8KB缓冲区
                int bytesRead;
                // 循环读取MinIO数据 → 写入响应流（边读边传）
                //stream.read(buffer)：从 MinIO 读取 8KB 数据到缓冲区；
                while ((bytesRead = stream.read(buffer)) != -1) {
                    //out.write(...)：立即将缓冲区中的数据写入响应流，发送给客户端；
                    out.write(buffer, 0, bytesRead);
                    //out.flush()：强制刷新流，确保数据实时推送，不积压。
                    out.flush();
                }
            }
        } catch (Exception e) {
            try {
                response.setContentType("application/json;charset=UTF-8");
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                response.getWriter().write("{\"code\":500,\"msg\":\"下载失败: " + e.getMessage() + "\"}");
                response.getWriter().flush();
            } catch (IOException ex) {
                throw new ServiceException("错误响应失败: " + ex.getMessage(), ex);
            }
            throw new ServiceException("下载失败: " + e.getMessage(), e);
        }
    }

    /**
     * 9. 按用户+类型分页查询（带缓存，解决穿透）
     */
    @Override
    public Result listByUserAndType(String fileType, Integer pageNum, Integer pageSize) {
        Long userId = SecurityUtils.getCurrentUserId();
        if (userId == null) {
            return Result.fail("未获取到当前用户信息，请登录");
        }

        // 处理分页参数
        pageNum = (pageNum == null || pageNum < 1) ? 1 : pageNum;
        pageSize = (pageSize == null || pageSize < 1) ? 10 : pageSize;

        // 缓存键：media:user:type:123:图片:1:10（用户ID+类型+页码+页大小）
        String cacheKey = CACHE_MEDIA_USER_TYPE_KEY + userId + ":" + fileType + ":" + pageNum + ":" + pageSize;

        // 1. 先查缓存
        String json = stringRedisTemplate.opsForValue().get(cacheKey);
        if (StringUtils.isNotBlank(json)) {
            try {
                // 使用具体的Page类而非IPage接口进行反序列化
                Page<MediaResourceVO> voPage = JSONUtil.toBean(json, new TypeReference<Page<MediaResourceVO>>() {
                }, true);
                if (voPage != null && voPage.getRecords() != null) {
                    return Result.ok(voPage);
                } else {
                    log.warn("缓存数据格式异常，键：{}", cacheKey);
                }
            } catch (Exception e) {
                log.error("缓存反序列化失败，键：{}", cacheKey, e);
            }
        }


        // 2. 缓存未命中，查数据库
        Page<MediaResource> page = new Page<>(pageNum, pageSize);
        LambdaQueryWrapper<MediaResource> lqw = new LambdaQueryWrapper<>();
        lqw.eq(MediaResource::getUploaderId, userId)
                .eq(MediaResource::getFileType, fileType)
                .orderByDesc(MediaResource::getCreatedAt,
                        MediaResource::getViews,
                        MediaResource::getLikeCount,
                        MediaResource::getCollectionCount);
        Page<MediaResource> resultPage = this.page(page, lqw);

        // 3. 转换为VO并写入缓存（缓存时间30分钟，随机偏移）
        IPage<MediaResourceVO> voPage = resultPage.convert(this::convertToVO);
        if (voPage != null && voPage.getRecords() != null) {
            log.info("缓存写入成功，键：{}", cacheKey);
            long randomTime = cacheClient.randomObjectTimes(1800L);
            cacheClient.set(cacheKey, voPage, randomTime, TimeUnit.SECONDS);
        } else {
            log.warn("查询结果为空，不写入缓存，键：{}", cacheKey);
        }

        return Result.ok(voPage);
    }

    /**
     * 10. 更新媒体资源（更新操作，删除缓存）
     */
    @Override
    public Result updateMediaById(Long id, MediaResourceDTO dto) {
        // 1. 基础参数校验
        if (ObjectUtils.isEmpty(id) || ObjectUtils.isEmpty(dto)) {
            return Result.fail("参数不能为空");
        }

        // 2. 获取当前登录用户ID
        Long userId = SecurityUtils.getCurrentUserId();
        if (ObjectUtils.isEmpty(userId)) {
            return Result.fail("未获取到当前登录用户信息，请重新登录");
        }

        // 3. 查询当前用户名下的目标媒体资源
        LambdaQueryWrapper<MediaResource> lqw = new LambdaQueryWrapper<>();
        lqw.eq(MediaResource::getUploaderId, userId)
                .eq(MediaResource::getId, id);
        MediaResource mediaResource = this.getOne(lqw);

        // 4. 校验资源是否存在
        if (ObjectUtils.isEmpty(mediaResource)) {
            return Result.fail("未找到该媒体资源，或您无权限修改此资源");
        }

        try {
            // 拷贝DTO属性到实体
            BeanUtils.copyProperties(dto, mediaResource);
        } catch (Exception e) {
            log.error("DTO数据拷贝到实体时发生异常", e);
            return Result.fail("数据处理失败，请稍后重试");
        }

        // 5. 执行更新操作
        boolean updateSuccess = this.updateById(mediaResource);
        if (!updateSuccess) {
            return Result.fail("媒体资源更新失败，请重试");
        }

        // 6. 缓存更新策略：删除所有相关缓存
        String fileType = mediaResource.getFileType();
        cacheClient.deleteByPattern(CACHE_MEDIA_INFO_KEY + id); // 单个资源缓存
        cacheClient.deleteByPattern(CACHE_MEDIA_TYPE_KEY + fileType + ":*"); // 类型分页缓存
        cacheClient.deleteByPattern(CACHE_MEDIA_USER_KEY + userId + ":*"); // 用户分页缓存
        cacheClient.deleteByPattern(CACHE_MEDIA_USER_TYPE_KEY + userId + ":" + fileType + ":*"); // 用户+类型缓存

        return Result.ok("媒体资源更新成功");
    }
}
